package magnet.processor.instances.aspects.disposer;

import magnet.processor.common.ValidationException;
import magnet.processor.instances.parser.AttributeParser;
import magnet.processor.instances.parser.ParserInstance;

import javax.lang.model.element.*;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import java.util.List;

public class DisposerAttributeParser extends AttributeParser {

    private static DisposerAttributeParser INSTANCE;

    private DisposerAttributeParser() {
        super("disposer");
    }

    public static DisposerAttributeParser INSTANCE() {
        synchronized (DisposerAttributeParser.class) {
            if (INSTANCE == null) {
                INSTANCE = new DisposerAttributeParser();
            }
        }
        return INSTANCE;
    }

    @Override
    public <E extends Element> ParserInstance<E> parse(Scope<E> scope, AnnotationValue value) throws ValidationException {
        ParserInstance copy = scope.getInstance().copy();
        copy.setDisposer(parseMethodName(scope, value));
        return copy;
    }

    private <E extends Element> String parseMethodName(Scope<E> scope, AnnotationValue value) throws ValidationException {
        Element element = scope.getElement();
        if (element.getKind() != ElementKind.CLASS) {
            throw new ValidationException(element, "Disposer can be defined for annotated class only.");
        }


        String methodName = scope.getEnv().getAnnoation()
                .getStringValue(value).replace("\"", "");

        Element methodElement = null;
        List<? extends Element> methodElements = element.getEnclosedElements();
        for (int i = 0; i < methodElements.size(); i++) {
            Element it = methodElements.get(i);
            if (it.getKind() == ElementKind.METHOD && it.getSimpleName().toString().equals(methodName)) {
                methodElement = it;
                break;
            }
        }
        if (methodElement == null) {
            throw new ValidationException(element, "Instance must declare disposer method" + methodName + ".");
        }

        TypeMirror returnType = null;
        if (methodElement instanceof ExecutableElement) {
            returnType = ((ExecutableElement) methodElement).getReturnType();
            if (returnType.getKind() != TypeKind.VOID) {
                throw new ValidationException(element, "Disposer method" + methodName + " must return void.");
            }

            if (((ExecutableElement) methodElement).getParameters().size() != 0) {
                throw new ValidationException(element, "Disposer method" + methodName + " must have no parameters.");
            }
        }

        if (methodElement.getModifiers().contains(Modifier.PRIVATE)) {
            throw new ValidationException(element, "Disposer method" + methodName + " must not be 'private'.");
        }

        return methodName;
    }
}
